#include "my_printf.h"
#include "string_utils.h"
#include "type.h"
#include "uart.h"

#define NOR_FLASH_BASE 0

void nor_write_word(uint32 base, uint32 offset, uint16 val) {
    volatile uint16 *p = (volatile uint16 *)(base + (offset << 1));
    *p = val;
}

void nor_cmd(uint32 offset, uint cmd) {
    nor_write_word(NOR_FLASH_BASE, offset, cmd);
}

uint32 nor_read_word(uint32 base, uint32 offset) {
    volatile uint16 *p = (volatile uint16 *)(base + (offset << 1));
    return *p;
}

uint32 nor_dat(uint32 offset) { return nor_read_word(NOR_FLASH_BASE, offset); }

void do_scan_nor_falsh(void) {
    char str[4];
    uint32 size = 0;
    uint32 regions = 0;
    uint32 region_info_base = 0;
    uint32 block_addr, blocks, block_size, i, j;

    int vendor, device;

    nor_cmd(0x555, 0xaa);
    nor_cmd(0x2aa, 0x55);
    nor_cmd(0x555, 0x90);
    vendor = nor_dat(0);
    device = nor_dat(1);
    nor_cmd(0x00, 0xf0);

    nor_cmd(0x55, 0x98);
    str[0] = nor_dat(0x10);
    str[1] = nor_dat(0x11);
    str[2] = nor_dat(0x12);
    str[3] = '\0';
    printf("str = %s\r\n", str);

    size = 1 << (nor_dat(0x27));

    printf("vendor id = 0x%x, device id = 0x%x, nor size = 0x%x, %dM\n\r",
           vendor, device, size, size / (1024 * 1024));

    regions = nor_dat(0x2c);
    region_info_base = 0x2d;
    block_addr = 0;
    printf("Block/Sector start Address:\r\n");
    uint8 cnt = 0;
    for (i = 0; i < regions; i++) {
        blocks = 1 + nor_dat(region_info_base) +
                 (nor_dat(region_info_base + 1) << 8);
        block_size = 256 * (nor_dat(region_info_base + 2) +
                            (nor_dat(region_info_base + 3) << 8));
        region_info_base += 4;

        // printf(
        //     "\n\rregion %d, blocks = %d, block_size = 0x%x, block_addr = "
        //     "0x%x\n\r",
        //     i, blocks, block_size, block_addr);

        for (j = 0; j < blocks; j++) {
            // printf("0x%08x ", block_addr);
            printHex(block_addr);
            putchar(' ');
            cnt++;
            block_addr += block_size;
            if (cnt % 5 == 0) {
                puts("\r\n");
            }
        }
    }
    puts("\n\r");
    /* 退出CFI模式 */
    nor_cmd(0, 0xf0);
}

static void wait_ready(uint32 addr) {
    uint32 val;
    uint32 pre;

    pre = nor_dat(addr >> 1);
    val = nor_dat(addr >> 1);
    while ((val & (1 << 6)) != (pre & (1 << 6))) {
        pre = val;
        val = nor_dat(addr >> 1);
    }
}

void do_erase_nor_flash() {
    unsigned int addr;

    printf("Enter the address to erase: ");
    addr = get_uint();
    printf("erasing...\r\n");

    nor_cmd(0x555, 0xaa);
    nor_cmd(0x2aa, 0x55);
    nor_cmd(0x555, 0x80);

    nor_cmd(0x555, 0xaa);
    nor_cmd(0x2aa, 0x55);
    nor_cmd(addr >> 1, 0x30);

    wait_ready(addr);
}

void do_write_nor_flash() {
    uint32 addr;
    uint8 str[100];
    uint16 val;
    int i, j;

    printf("Enter the address to write: ");
    addr = get_uint();
    printf("Enter the string to write: ");
    gets(str);

    printf("writing ...\r\n");
    i = 0;
    j = 1;
    while (str[i] && str[j]) {
      val = str[i] + (str[j] << 8);
      nor_cmd(0x555, 0xaa);
      nor_cmd(0x2aa, 0x55);
      nor_cmd(0x555, 0xa0);
      nor_cmd(addr >> 1, val);
      wait_ready(addr);
      i += 2;
      j += 2;
      addr += 2;
    }
    val = str[i];
    nor_cmd(0x555, 0xaa);
    nor_cmd(0x2aa, 0x55);
    nor_cmd(0x555, 0xa0);
    nor_cmd(addr >> 1, val);
    wait_ready(addr);
}

void do_read_nor_flash() {
    uint32 addr;
    volatile uint8 *p;
    int i, j;
    uint8 c;
    uint8 str[16];

    printf("Enter the address to read: ");
    addr = get_uint();

    p = (volatile uint8 *)addr;

    printf("Data:\r\n");
    for (i = 0; i < 4; i++) {
      for (j = 0; j < 16; j++) {
        c = *p++;
        str[j] = c;
        printf("%02x ", c);
      }
      printf("      ; ");
      for (j = 0; j < 16; j++) {
        if (str[j] < 0x20 || str[j] > 0x7e) {
          putchar('.');
        } else {
          putchar(str[j]);
        }
      }
      printf("\r\n");
    }
}

void nor_flash_test(void) {
    char c;

    while (1) {
        puts("[s] Scan nor flash\r\n");
        puts("[e] Erase nor flash\r\n");
        puts("[w] Write nor flash\r\n");
        puts("[r] Read nor flash\r\n");
        puts("[q] quit\r\n");
        puts("Enter selection: ");

        c = getchar();
        printf("%c\n\r", c);

        switch (c) {
            case 'q':
            case 'Q':
                return;
                break;
            case 's':
            case 'S':
                do_scan_nor_falsh();
                break;
            case 'e':
            case 'E':
                do_erase_nor_flash();
                break;
            case 'w':
            case 'W':
                do_write_nor_flash();
                break;
            case 'r':
            case 'R':
                do_read_nor_flash();
                break;
        }
    }
}
